/*
 * @Author: your name
 * @Date: 2020-05-18 10:49:05
 * @LastEditTime: 2020-05-21 10:54:11
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: \truck\platform\time_time\my_timer.c
 */

#include "my_timer.h"


static struct timeval orig,cur;
static uint32_t posix_timer_time=0;		/*in milisecond */

void posix_timer_init(void);
void posix_timer_do(void);
void posix_timer_uninit(void);
TinyTimer posix_timer={	0,
						posix_timer_init,
						posix_timer_do,
						posix_timer_uninit,
						// {1,0}};
						{0,POSIXTIMER_INTERVAL}};


void posix_timer_init(void)
{
	posix_timer.state=RTP_TIMER_RUNNING;
	gettimeofday(&orig,NULL);
	posix_timer_time=0;
}
void posix_timer_do(void)
{
	int diff,time;
	struct timeval tv;
	gettimeofday(&cur,NULL);
	time=((cur.tv_usec-orig.tv_usec)/1000 ) + ((cur.tv_sec-orig.tv_sec)*1000 );
	if ( (diff=time-posix_timer_time)>50){
		xeLOG_WARNING("Must catchup %i miliseconds.",diff);
	}

	while((diff = posix_timer_time-time) > 0)
	{
		tv.tv_sec = diff/1000;
		tv.tv_usec = (diff%1000)*1000;
		select(0,NULL,NULL,NULL,&tv);
		gettimeofday(&cur,NULL);
		time=((cur.tv_usec-orig.tv_usec)/1000 ) + ((cur.tv_sec-orig.tv_sec)*1000 );
	}
	posix_timer_time+=POSIXTIMER_INTERVAL/1000;
	
}

void posix_timer_uninit(void)
{
	posix_timer.state=RTP_TIMER_STOPPED;
}

int wait_point_check(WaitPoint *wp, uint32_t t){
	int ok=FALSE;

	if (wp->wakeup){
		if (TIME_IS_NEWER_THAN(t,wp->time)){
			wp->wakeup=FALSE;
			ok=TRUE;

		}
	}
	return ok;
}
#define wait_point_wakeup(wp) pthread_cond_signal(&(wp)->cond);

void rtp_session_process (t_session * session, uint32_t time, t_sched_para *sched)
{
	wait_point_lock(&session->wp);
	if (wait_point_check(&session->wp,time)){
		wait_point_wakeup(&session->wp);
	}
	wait_point_unlock(&session->wp);
}


void * timer_schedule(void * psched)
{
	t_sched_para *sched=(t_sched_para*)psched;
	TinyTimer *timer=sched->timer;
    t_session *current;

	pthread_mutex_lock(&sched->lock);
	pthread_cond_signal(&sched->unblock_select_cond);	/* unblock the starting thread */
	pthread_mutex_unlock(&sched->lock);

    timer->timer_init();
	while(sched->thread_running)
	{
        //TODO:
        current=sched->list;
		/* processing all scheduled rtp sessions */
		while (current!=NULL)
		{
			// STD_print("scheduler: processing session=0x%p.\n",current);
			rtp_session_process(current,sched->time_,sched);
			current=current->next;
		}
		timer->timer_do();
		sched->time_ += sched->timer_inc;
        // STD_print(" sched->time_:%u,sched->timer_inc:%d",sched->time_,sched->timer_inc);
	}

	timer->timer_uninit();
	return NULL;
}

void tiny_scheduler_set_timer(t_sched_para *sched,TinyTimer *timer)
{
	if (sched->thread_running){
		xeLOG_WARNING("Cannot change timer while the scheduler is running !!");
		return;
	}
	sched->timer=timer;
	/* report the timer increment */
	sched->timer_inc=(timer->interval.tv_usec/1000) + (timer->interval.tv_sec*1000);
    xeLOG_NOTICE("sched unit %d ms",sched->timer_inc);
}



int schedule_init(t_sched_para *sched)
{
    sched->time_=0;
    tiny_scheduler_set_timer(sched,&posix_timer);
	pthread_mutex_init(&sched->lock,NULL);
	pthread_cond_init(&sched->unblock_select_cond,NULL);
    return 0;
}

void tiny_scheduler_start(t_sched_para *sched)
{
    
	if (sched->thread_running==0){
		sched->thread_running=1;
		pthread_mutex_lock(&sched->lock);
		pthread_create(&sched->thread, NULL, timer_schedule,(void*)sched);
		pthread_cond_wait(&sched->unblock_select_cond,&sched->lock);
		pthread_mutex_unlock(&sched->lock);
		xeLOG_NOTICE("timer_schedule startup...");
	}
	else xeLOG_WARNING("Scheduler thread already running.");

}
void tiny_scheduler_stop(t_sched_para *sched)
{
	if (sched->thread_running==1)
	{
		sched->thread_running=0;
		pthread_join(sched->thread, NULL);
	}
	else xeLOG_WARNING("Scheduler thread is not running.");
}

t_sched_para g_timer_sched={0};
void timer_sched_init()
{
    memset(&g_timer_sched, 0 ,sizeof(t_sched_para));
    schedule_init(&g_timer_sched);
    tiny_scheduler_start(&g_timer_sched);
}


void wait_point_init(WaitPoint *wp){
	pthread_mutex_init(&wp->lock,NULL);
	pthread_cond_init(&wp->cond,NULL);
	wp->time=0;
	wp->wakeup=FALSE;
}
void wait_point_uninit(WaitPoint *wp){
	pthread_cond_destroy(&wp->cond);
	pthread_mutex_destroy(&wp->lock);
}

void wait_point_wakeup_at(WaitPoint *wp, uint32_t t, int dosleep){
	wp->time=t;
	wp->wakeup=TRUE;
	if (dosleep) pthread_cond_wait(&wp->cond,&wp->lock);
}

void scheduler_add_session(t_sched_para *sched, t_session *session)
{
	t_session *oldfirst;
	pthread_mutex_lock(&sched->lock);
	oldfirst=sched->list;
	sched->list=session;
	session->next=oldfirst;
	pthread_mutex_unlock(&sched->lock);
}

void user_session_deinit(t_session *session)
{
	t_session *tmp;
    t_sched_para *sched = &g_timer_sched;
	int cond=1;

    if(!session)
        return;
    pthread_mutex_lock(&sched->lock);
	tmp=sched->list;
	if (tmp==session){
		sched->list=tmp->next;
	    pthread_mutex_unlock(&sched->lock);
		return;
	}
	/* go the position of session in the list */
	while(cond){
		if (tmp!=NULL){
			if (tmp->next==session){
				tmp->next=tmp->next->next;
				cond=0;
			}
			else tmp=tmp->next;
		}else {
			/* the session was not found ! */
			xeLOG_ERR("the session was not found in the scheduler list!");
			cond=0;
		}
	}
	pthread_mutex_unlock(&sched->lock);
	usleep(10000);
	wait_point_uninit(&session->wp);
}




int user_session_init(t_session *session)
{
    session->sched = &g_timer_sched;
    wait_point_init(&session->wp);
    scheduler_add_session(&g_timer_sched, session);
    return 0;
}

int user_session_send_with_ts(t_session *session,uint32_t send_ts)
{
	uint32_t packet_time;
    t_sched_para *sched = session->sched;
    if(!session->is_start){

        session->snd_time_offset = sched->time_;
        session->snd_ts_offset = send_ts;
        session->is_start = 1;
        // xeLOG_DEBUG("first send_ts:%d snd_time_offset:%d",send_ts,session->snd_time_offset)
    }

    wait_point_lock(&session->wp);
    packet_time =(send_ts -
                    session->snd_ts_offset) +
                session->snd_time_offset;

    // xeLOG_DEBUG("packet_time:%d",packet_time);
    if (TIME_IS_STRICTLY_NEWER_THAN (packet_time, sched->time_))
    {
        wait_point_wakeup_at(&session->wp,packet_time,1);
    }
    wait_point_unlock(&session->wp);

    return 0;
}


// t_session session1={0};

// void timer_main()
// {
//     uint32_t ts= 0;
//     timer_sched_init();
//     user_session_init(&session1);

//     while(1){
//         ts += 1000;
//         user_session_send_with_ts(&session1,ts);
//         STD_print("send");
//         if(ts > 10000){
//             user_session_deinit(&session1);
//         }
//     }
// }